RailsをバックエンドにしたFlux(React + Alt)を試してみる
丹内です。
最近ReactJSを触り始めました。
掲題の通り、Railsをバックエンドにした場合のFluxを書いてみたのでまとめます。
このエントリの参考URLにあるリポジトリを参考にしました。
バックエンド
普通のRailsアプリです。Scaffoldをベースに、フロントエンドを書いていきます。
db/migtate/create_books.rb
class CreateBooks < ActiveRecord::Migration def change create_table :books do |t| t.string :title t.timestamps null: false end end end
BooksController#index
今回、ブラウザで/booksにアクセスした時に、最初に空っぽのHTMLを返し、ReactJSでAjaxリクエストによってデータを取得しDOMを構築する方法をとったため、indexアクションにJSONの場合を追加しています。
def index @books = Book.all respond_to do |format| format.html { render :index } format.json { render json: @books } end end
実装
rails newでアプリを生成したあと、Railsアプリ直下にclientという名前でディレクトリを作り、そこで適当にクライアントサイド開発環境を作ります。
app/books/index.html.slim
HTML(テンプレートエンジンはSlimです)は、これだけです。
h1 Listing books #content
ここからAjaxで取得したデータをDOMとしてくっつけます。
client/assets/javascripts/App.jsx
ここがクライアントサイドのエントリポイントとなります。つまり、ブラウザでhttp://localhost:3000/books
にアクセスしてページを読み込んだ直後にここから処理が始まります。
import $ from 'jquery'; import React from 'react'; import BookBox from './components/BookBox'; $(function onLoad() { function render() { React.render( <div> <BookBox url='books.json'/> </div>, document.getElementById('content') ); } render(); });
このBookBoxは別のReactClassとして定義してあり、それをimportして呼び出すだけの処理です。
client/assets/javascripts/components/BookBox.jsx
ReactJSの記述が本格的に始まります。まずは諸々のライフサイクルを定義しています。
import React from 'react'; import BookStore from '../stores/BookStore'; import BookActions from '../actions/BookActions'; import BookList from '../components/BookList'; const BookBox = React.createClass({ displayName: 'BookBox', propTypes: { url: React.PropTypes.string.isRequired }, getStoreState() { return { books: BookStore.getState() }; }, getInitialState() { return this.getStoreState(); }, componentDidMount() { BookStore.listen(this.onChange); BookActions.fetchBooks(this.props.url, true); }, componentWillUnmount() { BookStore.unlisten(this.onChange); }, onChange() { this.setState(this.getStoreState()); }, render() { return ( <div className="bookBox container"> <BookList books={ this.state.books.books }/> </div> ); } }); export default BookBox;
conponentDidMount()
で呼び出したBookStore.fetchBooks()
でAjaxリクエストを実行し、renderでその結果を参照しています。
client/assets/javascripts/actions/BookActions.js
ここからはReactJSではなくAltの領域になります。まずはActionです。
fetchBooks
でAjaxリクエストを送り、コールバックでこのクラスのupdateBooks
を呼んでいます。
少し分かりにくいのですが、その中のthis.dispatch
でStoreと連携させています。
import alt from '../FluxAlt'; import $ from 'jquery'; class BookActions { fetchBooks(url) { $.ajax({ url: url, dataType: 'json' }).then( (books) => this.actions.updateBooks(books), (errorMessage) => this.actions.updateBooksError(errorMessage) ); } updateBooks(books) { this.dispatch(books); } updateBooksError(errorMessage) { this.dispatch(errorMessage); } } export default alt.createActions(BookActions);
ここでimportしているFluxAlt
は、以下のようなユーティリティです。(client/assets/javascripts/FluxAlt.js)
import Alt from 'alt'; const alt = new Alt(); export default alt;
client/assets/javascripts/stores/BookStore.js
ここもAltです。Fluxで言うところのStoreです。
ポイントはconstructor
内のthis.bindListeners
で、ここでActionからdispatchした関数とStore内の関数を結びつけています。
import alt from '../FluxAlt'; import BookActions from '../actions/BookActions'; class BookStore { constructor() { this.books = []; this.errorMessage = null; this.bindListeners({ handleFetchBooks: BookActions.FETCH_BOOKS, handleUpdateBooks: BookActions.UPDATE_BOOKS, handleUpdateBooksError: BookActions.UPDATE_BOOKS_ERROR }); } handleFetchBooks() { return false; } handleUpdateBooks(books) { this.books = books; this.errorMessage = null; } handleUpdateBooksError(errorMessage) { this.errorMessage = errorMessage; } } export default alt.createStore(BookStore, 'BookStore');
その他コンポーネント
以下、client/assets/javascripts/components/BookBox.jsx
から読んでいるComponentです。
まずはclient/assets/javascripts/components/BookList.jsx
import React from 'react'; import Book from './Book'; const BookList = React.createClass({ displayName: 'BookList', propTypes: { books: React.PropTypes.array }, render() { const data = this.props.books; const bookNodes = data.map((book, index) => { return( <Book title={book.title} key={index}/> ); }); return ( <div className="bookList"> { bookNodes } </div> ) } }); export default BookList;
そしてBookListの中で読んでいるclient/assets/javascripts/components/Book.jsx
import React from 'react'; const Book = React.createClass({ displayName: 'Book', propTypes: { title: React.PropTypes.string.isRequired }, render() { return( <ul>{this.props.title}</ul> ); } }); export default Book;
まとめ
RailsアプリにAjaxリクエストを送るFluxを、Altを使って実装してみました。
クライアントサイドにはまだ入門したてなのですが、Node.JSを使ったフロントエンド環境構築だけで数日かかってしまいました。
RailsのAsset Pipelineにいい感じに載せられて開発効率を上げられるようにアセット管理をしつつ、もっとフロントエンド開発をして行きたいです。